package org.andengine.opengl.util;

import android.opengl.GLES20;

import org.andengine.util.exception.AndEngineRuntimeException;

/**
 * TODO Measure performance against regular Java Stack<E>
 * <p/>
 * (c) 2013 Zynga Inc.
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @author Michal Stawinski <michal.stawinski@gmail.com>
 * @since 14:49:23 - 02.05.2013
 */
public class GLScissorStack {
    // ===========================================================
    // Constants
    // ===========================================================

    public static final int GLSCISSOR_X_INDEX = 0;
    public static final int GLSCISSOR_Y_INDEX = 1;
    public static final int GLSCISSOR_WIDTH_INDEX = 2;
    public static final int GLSCISSOR_HEIGHT_INDEX = 3;

    public static final int GLSCISSORSTACK_DEPTH_MAX = 32;
    public static final int GLSCISSOR_SIZE = 4;

    private static final int GLSCISSORSTACKOFFSET_UNDERFLOW = -1 * GLScissorStack.GLSCISSOR_SIZE;
    private static final int GLSCISSORSTACKOFFSET_OVERFLOW = GLScissorStack.GLSCISSORSTACK_DEPTH_MAX * GLScissorStack.GLSCISSOR_SIZE;

    // ===========================================================
    // Fields
    // ===========================================================

    private final int[] mScissorStack = new int[GLScissorStack.GLSCISSORSTACK_DEPTH_MAX * GLScissorStack.GLSCISSOR_SIZE];
    private int mScissorStackOffset;

    // ===========================================================
    // Constructors
    // ===========================================================

    public GLScissorStack() {

    }

    // ===========================================================
    // Constructors
    // ===========================================================

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public int[] getScissor(final int[] pScissors) {
        if (this.mScissorStackOffset - GLScissorStack.GLSCISSOR_SIZE <= GLScissorStack.GLSCISSORSTACKOFFSET_UNDERFLOW) {
            throw new GLScissorStackUnderflowException();
        }

        System.arraycopy(this.mScissorStack, this.mScissorStackOffset, pScissors, 0, GLScissorStack.GLSCISSOR_SIZE);
        return pScissors;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    public void glPushScissor(final int pX, final int pY, final int pWidth, final int pHeight) {
        if (this.mScissorStackOffset + GLScissorStack.GLSCISSOR_SIZE >= GLScissorStack.GLSCISSORSTACKOFFSET_OVERFLOW) {
            throw new GLScissorStackOverflowException();
        }

        final int x;
        final int y;
        final int width;
        final int height;
        if (this.mScissorStackOffset == 0) {
            x = pX;
            y = pY;
            width = pWidth;
            height = pHeight;
        } else {
            /* Take the intersection between the current and the new scissor: */
            final int currentX = this.mScissorStack[this.mScissorStackOffset - (GLSCISSOR_SIZE - GLSCISSOR_X_INDEX)];
            final int currentY = this.mScissorStack[this.mScissorStackOffset - (GLSCISSOR_SIZE - GLSCISSOR_Y_INDEX)];
            final int currentWidth = this.mScissorStack[this.mScissorStackOffset - (GLSCISSOR_SIZE - GLSCISSOR_WIDTH_INDEX)];
            final int currentHeight = this.mScissorStack[this.mScissorStackOffset - (GLSCISSOR_SIZE - GLSCISSOR_HEIGHT_INDEX)];

            final float xMin = Math.max(currentX, pX);
            final float xMax = Math.min(currentX + currentWidth, pX + pWidth);

            final float yMin = Math.max(currentY, pY);
            final float yMax = Math.min(currentY + currentHeight, pY + pHeight);

            x = (int) xMin;
            y = (int) yMin;
            width = (int) (xMax - xMin);
            height = (int) (yMax - yMin);
        }

        this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_X_INDEX] = pX;
        this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_Y_INDEX] = pY;
        this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_WIDTH_INDEX] = pWidth;
        this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_HEIGHT_INDEX] = pHeight;

        GLES20.glScissor(x, y, width, height);

        this.mScissorStackOffset += GLScissorStack.GLSCISSOR_SIZE;
    }

    public void glPopScissor() {
        if (this.mScissorStackOffset - GLScissorStack.GLSCISSOR_SIZE <= GLScissorStack.GLSCISSORSTACKOFFSET_UNDERFLOW) {
            throw new GLScissorStackUnderflowException();
        }

        this.mScissorStackOffset -= GLScissorStack.GLSCISSOR_SIZE;

        final int x = this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_X_INDEX];
        final int y = this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_Y_INDEX];
        final int width = this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_WIDTH_INDEX];
        final int height = this.mScissorStack[this.mScissorStackOffset + GLSCISSOR_HEIGHT_INDEX];

        GLES20.glScissor(x, y, width, height);
    }

    public void reset() {
        this.mScissorStackOffset = 0;
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static class GLScissorStackOverflowException extends AndEngineRuntimeException {

        // ===========================================================
        // Constants
        // ===========================================================

        private static final long serialVersionUID = 8253962794945142016L;

        // ===========================================================
        // Fields
        // ===========================================================

        // ===========================================================
        // Constructors
        // ===========================================================

        public GLScissorStackOverflowException() {

        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }

    public static class GLScissorStackUnderflowException extends AndEngineRuntimeException {

        // ===========================================================
        // Constants
        // ===========================================================

        private static final long serialVersionUID = -2962655760038324091L;

        // ===========================================================
        // Fields
        // ===========================================================

        // ===========================================================
        // Constructors
        // ===========================================================

        public GLScissorStackUnderflowException() {

        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }
}
